﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;
using Xeml.Document.Contracts;
using System.Diagnostics;
using Xeml.Document.EnvironmentalOntology;
using Xeml.Document.Manager;
using System.Threading.Tasks;

//potentially interesting and could be imported in the future:
//http://obo.cvs.sourceforge.net/viewvc/obo/obo/ontology/phenotype/environment/environment_ontology.obo

namespace Xeml.Document.Ontologies.OBO
{
    public abstract class OboHandler : IHierarchicalView
    {
        public string ComponentName;
        protected abstract void DoTagTypeAnnotation(ParsedOboFile collection);

        #region constructors
        protected OboHandler()
        {
            _terms = new List<IDynamicOntologyTerm>();
            _nodes = new List<TermNode>();
            OntologyLoadState = OntologyLoadState.Unloaded;
        }
        #endregion constructors

        #region IDynamicOntologyHandler Members
        protected List<IDynamicOntologyTerm> _terms;
        public IEnumerable<IDynamicOntologyTerm> Terms
        {
            get
            {
                return _terms;
            }
        }

        public IDynamicOntologyTerm Find(Term t)
        {
            return Find(t.TermId);
        }

        public IDynamicOntologyTerm Find(string termId)
        {
            return _terms.AsParallel().AsUnordered().Single(x => x.TermId == termId);
        }
        #endregion IDynamicOntologyHandler

        #region IHierarchicalView Members
        protected List<TermNode> _nodes;
        public IEnumerable<TermNode> Nodes
        {
            get { return _nodes; }
        }

        public TermNode FindNode(IOntologyTerm t)
        {
            return FindNode(t.TermId);
        }

        public TermNode FindNode(string termid)
        {
            foreach (var item in _nodes)
            {
                if (FindNode(item, termid) != null)
                    return FindNode(item, termid);
            }
            return null;
        }

        private TermNode FindNode(TermNode start, string termid)
        {
            TermNode ret = null;
            if (start.Term.TermId == termid)
                ret = start;
            else
                foreach (var item in start.Childs)
                {
                    if (FindNode(item.Relation, termid) != null)
                        ret = FindNode(item.Relation, termid);
                }

            return ret;
        }
        #endregion

        #region IOntologyHandler Members
        private string _ontologyLoadMessage;
        public string OntologyLoadMessage
        {
            get
            {
                return _ontologyLoadMessage;
            }
        }

        /// <summary>
        /// Indicates the load state fo the ontology.
        /// </summary>
        public OntologyLoadState OntologyLoadState
        {
            get;
            private set;
        }

        public OntologyType OntologyType
        {
            get { return OntologyType.Environment; }
        }

        public string NameSpace { get; private set; }
        public bool AutoValidation { get; set; }

        private bool _reloadNeeded;
        private string _instanceLocation;
        public string InstanceLocation
        {
            get
            {
                return _instanceLocation;
            }
            set
            {
                if (_instanceLocation != value)
                {
                    _instanceLocation = value;
                    _reloadNeeded = true;
                }
            }
        }

        private string _nameSpaceAlias;

        public void Load(string nameSpaceAlias)
        {
            Load(nameSpaceAlias, false);
        }

        public void Load(string nameSpaceAlias, bool force)
        {
            if (_nameSpaceAlias != nameSpaceAlias)
            {
                _nameSpaceAlias = nameSpaceAlias;
                _reloadNeeded = true;
            }
            if (_reloadNeeded || force)
            {
                _terms.Clear();
                _nodes.Clear();

                try
                {
                    //read file into attributelist
                    ParsedOboFile MasterCollection = null;
                    using (StringReader reader = new StringReader(OnlineCacheManager.Instance.DownloadContent(_instanceLocation)))
                    {
                        MasterCollection = new ParsedOboFile(reader);
                    }

                    //are there any unrecohnised terms???
                    List<string> errors = new List<string>();
                    foreach (var store in MasterCollection.Where(x => x.Any(y => y.Item1 == OboTags.XXX_unknown)))
                    {
                        foreach (var unknownString in store.Where(z => z.Item1 == OboTags.XXX_unknown))
                        {
                            errors.Add(unknownString.Item2);
                        }
                    }

#if DEBUG
                    //Debug.Assert(errors.Count() == 0);
#endif

                    //assign content to headers such as environment, context and enumeration
                    DoTagTypeAnnotation(MasterCollection);

                    //copy attributeLists into Xeoterms
                    foreach (var item in MasterCollection.Where(x => x.isEnvironmentVariableTerm))
                    {
                        XeoTerm xeo = item.GetTerm(NameSpace, _nameSpaceAlias);

                        Tuple<OboTags, string> tmp = item.SingleOrDefault(x => x.Item1 == OboTags.name);
                        if (tmp != null)
                        {
                            xeo.Name = tmp.Item2;
                        }
                        tmp = item.SingleOrDefault(x => x.Item1 == OboTags.def);
                        if (tmp != null)
                        {
                            xeo.Definition = tmp.Item2; ;
                        }
                        tmp = item.SingleOrDefault(x => x.Item1 == OboTags.@namespace);
                        if (tmp != null)
                        {
                            xeo.NameSpace = tmp.Item2;
                        }

                        if (xeo != null && !_terms.AsParallel().AsUnordered().Any(x => x.TermId == xeo.TermId))
                        {
                            _terms.Add(xeo);
                        }
                    }

                    //create the nodes
                    _nodes.AddRange(_terms.AsParallel().AsUnordered().Select(x => TermNode.CreateNode(x)));
                    _nodes.OrderBy(x => x.Label);

                    BuildContextes(MasterCollection);
                    Build_IsA_Hirachy(MasterCollection);
                    AddInheritedContext(MasterCollection);

                    SortNodes(_nodes);

                    DeleteHeaderNodes();

                    _reloadNeeded = false;
                    OntologyLoadState = OntologyLoadState.Loaded;

                    Parallel.ForEach(MasterCollection, x => { x.Clear(); });
                    MasterCollection.Clear();
                    MasterCollection.TrimExcess();
                }
                catch (Exception ex)
                {
                    OntologyLoadState = OntologyLoadState.LoadFailed;
                    _ontologyLoadMessage = ex.Message;
                    Debug.WriteLine(string.Concat(ComponentName, ". End ontology load with exception: ", ex.Message));
                    ValidationMessage vm = new ValidationMessage(SeverityCode.Error, "Ontology file load failed: '" + ex.Message + "'", this.ComponentName, false);
                    OnValidation(vm);
                }
            }
        }

        private void SortNodes(List<TermNode> collection)
        {
            collection.Sort(TermNode.CompareTermNodeByLabel);

            foreach (var item1 in collection.Select(x => x.Childs))
            {
                foreach (var item2 in item1)
                {
                    item2.Relation.SortChildren(TermNode.CompareAttributedRelationByTermNodeLabel);
                }
            }
        }

        private void Build_IsA_Hirachy(ParsedOboFile collection)
        {
            List<TermNode> processedNodes = new List<TermNode>();
            foreach (var current in collection.Where(x => x.isEnvironmentVariableTerm && x.Any(y => y.Item1 == OboTags.is_a)))
            {
                var tn = _nodes.Single(x => x.Term.TermId == current.Id);
                foreach (var attr in current.Where(x => x.Item1 == OboTags.is_a))
                {
                    var parent = _nodes.Find(x => x.Term.TermId == attr.Item2);
                    if (parent != null)
                    {
                        parent.IsContainer = true;
                        parent.AddSubNode(tn, "is_a");
                        if (!processedNodes.Contains(tn))
                        {
                            processedNodes.Add(tn);
                        }
                        _terms.Remove(parent.Term as IDynamicOntologyTerm);
                    }
                }
            }

            foreach (var tn in processedNodes)
            {
                _nodes.Remove(tn);
            }

            processedNodes.Clear();
            processedNodes.TrimExcess();
        }

        /// <summary>
        /// if only one rootnode is available this method deletes the root node and upgrade the child nodes to new rootnodes
        /// </summary>
        private void DeleteHeaderNodes()
        {
            if (_nodes.Count == 1)
            {
                List<TermNode> copy = new List<TermNode>(_nodes);
                _nodes.Clear();
                var rootnode = copy.Single();
                foreach (var node in rootnode.Childs)
                {
                    node.Relation.RemoveParent();
                    _nodes.Add(node.Relation);
                }
                _terms.Remove(rootnode.Term as IDynamicOntologyTerm);
            }
        }

        private void AddInheritedContext(ParsedOboFile collection)
        {
            foreach (OboStanza item in collection.AsParallel().AsUnordered().Where(x => x.term != null))
            {
                foreach (Tuple<OboTags, string> attr in item.Where(x => x.Item1 == OboTags.is_a))
                {
                    AddInheritedContext(collection, item.term, attr.Item2);
                }
            }
        }

        private void AddInheritedContext(ParsedOboFile collection, XeoTerm targetXeoTerm, string currentId)
        {
            OboStanza current = collection.AsParallel().AsUnordered().Single(x => x.Id == currentId);

            //add the contextes from curent if not already available
            foreach (var item in current.contextCollection)
            {
                if (!targetXeoTerm.ContextCollection.ContainsKey(item.Name))
                {
                    targetXeoTerm.ContextCollection.Add(item.Name, item);
                }
            }

            //do recursive calls upwards following the 'is_a' relation
            foreach (var item in current.Where(x => x.Item1 == OboTags.is_a))
            {
                AddInheritedContext(collection, targetXeoTerm, item.Item2);
            }
        }

        protected abstract void BuildContextes(ParsedOboFile collection);

        protected VariableContextSpec CreateContext(ParsedOboFile collection, OboStanza currentXeoTermWithContext, string contextID, string ContextName)
        {
            VariableContextSpec context = new VariableContextSpec();
            context.UnitSet = new UnitSet();
            context.TypeDefine = new TypeSpecification();
            context.Name = ContextName;
            if (currentXeoTermWithContext.Any(x => x.Item1 == OboTags.def))
            {//if description available then set the description
                context.Description = currentXeoTermWithContext.Single(x => x.Item1 == OboTags.def).Item2;
            }

            var contextAttributeStore = collection.Single(x => x.Id == contextID);
            if (contextAttributeStore.Any(x => x.Item1 == OboTags.has_enum))
            {
                OboStanza enumAttributeStore = collection.Single(x => x.Id == contextAttributeStore.Single(y => y.Item1 == OboTags.has_enum).Item2);
                AddEnum(collection, context, enumAttributeStore.Id);
            }

            if (currentXeoTermWithContext.Any(y => y.Item1 == OboTags.is_a))
            {
                OboStanza DataTaype = collection.Single(x => x.Id == contextAttributeStore.Single(y => y.Item1 == OboTags.is_a).Item2);

                if (DataTaype.Name == "Bool")
                {
                    TypeSpecifics ts = new Xeml.Document.EnvironmentalOntology.Enum();
                    ts.TextVal = Boolean.TrueString;
                    context.TypeDefine.TypeSpecifica.Add(ts);

                    ts = new Xeml.Document.EnvironmentalOntology.Enum();
                    ts.TextVal = Boolean.FalseString;
                    context.TypeDefine.TypeSpecifica.Add(ts);

                    context.DefaultValue = Boolean.FalseString;
                }
            }

            DefineContextDataType(collection, context, contextID);

            return context;
        }

        protected void CreateContextUnitSet(ParsedOboFile collection, VariableContextSpec context, string contextID)
        {
            foreach (var item in collection.Where(x => x.StanzaType == OboStanzaTypes.Instance && x.Any(y => y.Item1 == OboTags.instance_of && y.Item2 == contextID)))
            {
                Debug.Assert(item.Count(x => x.Item1 == OboTags.name) == 1);
                Tuple<OboTags, string> symbol = item.SingleOrDefault(x => x.Item1 == OboTags.has_symbol);
                if (symbol != null)
                {
                    Debug.Assert(item.Count(x => x.Item1 == OboTags.name) == 1);
                    if (context.UnitSet.DefaultUnit == null)
                    {
                        Unit u = new Unit();
                        u.Name = item.Single(x => x.Item1 == OboTags.name).Item2;
                        u.Symbol = symbol.Item2;
                        context.UnitSet.DefaultUnit = u;
                    }
                    else
                    {
                        ConvertableUnit cu = new ConvertableUnit();
                        cu.Name = item.Single(x => x.Item1 == OboTags.name).Item2;
                        cu.Symbol = symbol.Item2;
                        cu.ConversionString = "n.a.";
                        cu.DefaultUnit = context.UnitSet.DefaultUnit;
                        context.UnitSet.ConvertableUnits.Add(cu);
                    }
                }
            }
        }

        protected void CreateContextEnumeration(ParsedOboFile collection, VariableContextSpec context, string contextID)
        {
            AddEnum(collection, context, contextID);
        }

        private void AddEnum(ParsedOboFile collection, VariableContextSpec context, string instanceId)
        {
            foreach (var item in collection.Where(x => x.StanzaType == OboStanzaTypes.Instance && x.Any(y => y.Item1 == OboTags.instance_of && y.Item2 == instanceId)))
            {
                TypeSpecifics ts = new Xeml.Document.EnvironmentalOntology.Enum();
                ts.TextVal = item.Single(x => x.Item1 == OboTags.name).Item2; ;
                context.TypeDefine.TypeSpecifica.Add(ts);
            }

            if (context.TypeDefine.TypeSpecifica.Count > 0)
            {
                context.TypeDefine.TypeSpecifica.Sort(TypeSpecifics.CompareTypeSpecificsByName);
                context.DefaultValue = context.TypeDefine.TypeSpecifica.Select(x => x.TextVal).First();
            }
        }

        private static void DefineContextDataType(ParsedOboFile collection, VariableContextSpec context, string contextID)
        {
            OboStanza contextAttributeStore = collection.Single(x => x.Id == contextID);
            if (contextAttributeStore.Any(OboTags.has_datatype))
            {
                OboStanza contextDataTypeParentStore = collection.Single(x => x.Id == contextAttributeStore.Single(y => y.Item1 == OboTags.has_datatype).Item2);
                switch (contextDataTypeParentStore.Single(x => x.Item1 == OboTags.name).Item2)
                {
                    case "FreeTextContext":
                    case "Text":
                        context.TypeDefine.BaseType = VariableDataTypes.Text;
                        break;
                    case "Number":
                        context.TypeDefine.BaseType = VariableDataTypes.Number;
                        context.TypeDefine.TypeSpecifica.Add(new Min(decimal.MinValue));
                        context.TypeDefine.TypeSpecifica.Add(new Max(decimal.MaxValue));
                        break;
                    case "Date":
                        context.TypeDefine.BaseType = VariableDataTypes.Date;
                        break;
                    case "Bool":
                        context.TypeDefine.BaseType = VariableDataTypes.Bool;
                        break;
                    default:
                        break;
                }
            }
        }

        [Obsolete("kann das raus????", true)]
        protected virtual void OnValidation(SeverityCode sc, string message, object relatedObject)
        {
            if (Validation != null)
            {
                Validation(this, new ValidationMessage(sc, message, relatedObject, false));
            }
        }

        public event EventHandler<ValidationMessage> Validation;
        protected virtual void OnValidation(ValidationMessage vm)
        {
            if (Validation != null)
                Validation(this, vm);
        }

        public event LoadedEventHandler OnLoaded;
        #endregion IOntologyHandler Members
    }
}
